Goal: can machine learning methods help us to associate metabolites with leaf length?

Previously (script 11b2) I filtered out unnamed metabolites. Here I keep them all. but remove blank samples

library(glmnet)
library(relaimpo)
library(tidyverse)

get leaflength data

leaflength <- read_csv("../../plant/output/leaf_lengths_metabolite.csv") %>%
  mutate(pot=str_pad(pot, width=3, pad="0"),
         sampleID=str_c("wyo", genotype, pot, sep="_"), 
         group=str_c(soil,genotype,trt,sep="_")) %>%
  select(sampleID, group, genotype, trt, leaf_avg_std)  %>%
  filter(!str_detect(group, "BLANK"))

── Column specification ───────────────────────────────────────────────────────────────────────
cols(
  pot = col_double(),
  soil = col_character(),
  genotype = col_character(),
  trt = col_character(),
  leaf_avg = col_double(),
  leaf_avg_std = col_double()
)
length(unique(leaflength$group))
[1] 4
leaflength %>% arrange(sampleID)

get and wrangle metabolite data

met_raw <-read_csv("../input/metabolites_set1.csv")

── Column specification ───────────────────────────────────────────────────────────────────────
cols(
  .default = col_double(),
  tissue = col_character(),
  soil = col_character(),
  genotype = col_character(),
  autoclave = col_character(),
  time_point = col_character(),
  concatenate = col_character()
)
ℹ Use `spec()` for the full column specifications.
met <- met_raw %>% 
  mutate(pot=str_pad(pot, width = 3, pad = "0")) %>%
  mutate(sampleID=str_c("wyo", genotype, pot, sep="_")) %>%
  filter(soil!="BLANK") %>%
  select(sampleID, genotype, tissue, sample_mass = `sample_mass mg`, !submission_number:concatenate) %>%
  pivot_longer(!sampleID:sample_mass, names_to = "metabolite", values_to = "met_amount") %>%
  
  #adjust by sample mass
  mutate(met_per_mg=met_amount/sample_mass) %>%
  
  #scale and center
  group_by(metabolite, genotype, tissue) %>%
  mutate(met_per_mg=scale(met_per_mg),
         met_amt=scale(met_amount)
  ) %>% 
  pivot_wider(id_cols = sampleID, 
              names_from = c(tissue, metabolite), 
              values_from = starts_with("met_"),
              names_sep = "_")

met

split this into two data frames, one normalized by tissue amount and one not.

met_per_mg <- met %>% select(sampleID,  starts_with("met_per_mg")) %>%
  as.data.frame() %>% column_to_rownames("sampleID")
met_amt <- met %>% select(sampleID,  starts_with("met_amt")) %>%
  as.data.frame() %>% column_to_rownames("sampleID")

get leaf data order to match

leaflength <- leaflength[match(met$sampleID, leaflength$sampleID),]
leaflength

Calc PCAs:

normalized

leaf

met_per_mg.leaf_PCA <- met_per_mg %>% 
  select(matches("_leaf_")) %>%
  prcomp(center = FALSE, scale. = FALSE) #already centered and scaled
names(met_per_mg.leaf_PCA)
[1] "sdev"     "rotation" "center"   "scale"    "x"       
tibble(variance=met_per_mg.leaf_PCA$sdev^2, PC=str_c("PC", 
                                                      str_pad(1:length(met_per_mg.leaf_PCA$sdev), width = 2, pad="0"))) %>%
  mutate(percent_var=100*variance/sum(variance),  
         cumulative_var=cumsum(percent_var)) %>%
  magrittr::extract(1:15,) %>%
  ggplot(aes(x=PC, y=percent_var)) +
  geom_col(fill="skyblue") + 
  geom_line(aes(y=cumulative_var), group="") +
  ggtitle("percent variance explained, named, normalized leaf metabolites")

some t.tests

met_per_mg.leaf_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  summarise(across(starts_with("PC"), ~ t.test(.x ~ trt)$p.value)) %>%
  pivot_longer(starts_with("PC")) %>%
  arrange(value)
Joining, by = "sampleID"

root

met_per_mg.root_PCA <- met_per_mg %>% 
  select(matches("_root_")) %>%
  prcomp(center = FALSE, scale. = FALSE) #already centered and scaled
names(met_per_mg.root_PCA)
[1] "sdev"     "rotation" "center"   "scale"    "x"       
tibble(variance=met_per_mg.root_PCA$sdev^2, PC=str_c("PC", 
                                                      str_pad(1:length(met_per_mg.root_PCA$sdev), width = 2, pad="0"))) %>%
  mutate(percent_var=100*variance/sum(variance),  
         cumulative_var=cumsum(percent_var)) %>%
  magrittr::extract(1:15,) %>%
  ggplot(aes(x=PC, y=percent_var)) +
  geom_col(fill="skyblue") + 
  geom_line(aes(y=cumulative_var), group="") +
  ggtitle("percent variance explained, named, normalized root metabolites")

met_per_mg.root_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  summarise(across(starts_with("PC"), ~ t.test(.x ~ trt)$p.value)) %>%
  pivot_longer(starts_with("PC")) %>%
  arrange(value)
Joining, by = "sampleID"

PC2 and marginally PC5

met_per_mg.root_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  summarise(across(starts_with("PC"), ~ t.test(.x ~ genotype)$p.value)) %>%
  pivot_longer(starts_with("PC")) %>%
  arrange(value)
Joining, by = "sampleID"

raw

leaf

met_amt.leaf_PCA <- met_amt %>%
  select(matches("_leaf_")) %>%
  prcomp(center = FALSE, scale. = FALSE) #already centered and scaled
names(met_per_mg.leaf_PCA)
[1] "sdev"     "rotation" "center"   "scale"    "x"       
tibble(variance=met_amt.leaf_PCA$sdev^2, PC=str_c("PC", 
                                                   str_pad(1:length(met_amt.leaf_PCA$sdev), width = 2, pad="0"))) %>%
  mutate(percent_var=100*variance/sum(variance),  
         cumulative_var=cumsum(percent_var)) %>%
  magrittr::extract(1:15,) %>%
  ggplot(aes(x=PC, y=percent_var)) +
  geom_col(fill="skyblue") + 
  geom_line(aes(y=cumulative_var), group="") +
  ggtitle("percent variance explained, named, raw leaf metabolites")

t.tests

met_amt.leaf_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  summarise(across(starts_with("PC"), ~ t.test(.x ~ trt)$p.value)) %>%
  pivot_longer(starts_with("PC")) %>%
  arrange(value)
Joining, by = "sampleID"
met_amt.leaf_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  summarise(across(starts_with("PC"), ~ t.test(.x ~ genotype)$p.value)) %>%
  pivot_longer(starts_with("PC")) %>%
  arrange(value)
Joining, by = "sampleID"

root

met_amt.root_PCA <- met_amt %>%
  select(matches("_root_")) %>%
  prcomp(center = FALSE, scale. = FALSE) #already centered and scaled
names(met_per_mg.root_PCA)
[1] "sdev"     "rotation" "center"   "scale"    "x"       
tibble(variance=met_amt.root_PCA$sdev^2, PC=str_c("PC", 
                                                   str_pad(1:length(met_amt.root_PCA$sdev), width = 2, pad="0"))) %>%
  mutate(percent_var=100*variance/sum(variance),  
         cumulative_var=cumsum(percent_var)) %>%
  magrittr::extract(1:15,) %>%
  ggplot(aes(x=PC, y=percent_var)) +
  geom_col(fill="skyblue") + 
  geom_line(aes(y=cumulative_var), group="") +
  ggtitle("percent variance explained, named, raw root metabolites")

t.test

met_amt.root_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  summarise(across(starts_with("PC"), ~ t.test(.x ~ trt)$p.value)) %>%
  pivot_longer(starts_with("PC")) %>%
  arrange(value)
Joining, by = "sampleID"
met_amt.root_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  summarise(across(starts_with("PC"), ~ t.test(.x ~ genotype)$p.value)) %>%
  pivot_longer(starts_with("PC")) %>%
  arrange(value)
Joining, by = "sampleID"

now try these in a penalized regression

normalized

are the PCs normalized?

colMeans(met_amt.leaf_PCA$x) %>% round(3) #yes centered
 PC1  PC2  PC3  PC4  PC5  PC6  PC7  PC8  PC9 PC10 PC11 PC12 PC13 PC14 PC15 PC16 PC17 PC18 PC19 
   0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0    0 
PC20 PC21 PC22 PC23 PC24 
   0    0    0    0    0 
apply(met_amt.leaf_PCA$x, 2, sd) %>% round(2) #not scaled
  PC1   PC2   PC3   PC4   PC5   PC6   PC7   PC8   PC9  PC10  PC11  PC12  PC13  PC14  PC15 
11.89  9.03  6.73  6.47  5.80  5.78  5.37  5.08  4.58  4.40  4.36  4.35  4.03  3.83  3.76 
 PC16  PC17  PC18  PC19  PC20  PC21  PC22  PC23  PC24 
 3.64  3.55  3.46  3.30  3.19  3.14  3.06  0.00  0.00 

combine the leaf and root, and then scale them:

met_per_mg.leaf_PCs <- met_per_mg.leaf_PCA$x
colnames(met_per_mg.leaf_PCs) <- str_c("leaf_", colnames(met_per_mg.leaf_PCs))

met_per_mg.root_PCs <- met_per_mg.root_PCA$x
colnames(met_per_mg.root_PCs) <- str_c("root_", colnames(met_per_mg.root_PCs))

met_per_mg.PCs <- cbind(met_per_mg.leaf_PCs, met_per_mg.root_PCs) %>%
  scale()

met_amt.leaf_PCs <- met_amt.leaf_PCA$x
colnames(met_amt.leaf_PCs) <- str_c("leaf_", colnames(met_amt.leaf_PCs))

met_amt.root_PCs <- met_amt.root_PCA$x
colnames(met_amt.root_PCs) <- str_c("root_", colnames(met_amt.root_PCs))

met_amt.PCs <- cbind(met_amt.leaf_PCs, met_amt.root_PCs) %>%
  scale()

also combine the rotations

met_per_mg.leaf_rotation <- met_per_mg.leaf_PCA$rotation %>%
  as.data.frame() %>% 
  rename_with(~ str_c("leaf_", .x)) %>%
  rownames_to_column("metabolite")

met_per_mg.root_rotation <- met_per_mg.root_PCA$rotation %>%
  as.data.frame() %>% 
  rename_with(~ str_c("root_", .x)) %>%
  rownames_to_column("metabolite")

met_per_mg.PC_rotation <- full_join(met_per_mg.leaf_rotation, met_per_mg.root_rotation, by="metabolite")

met_amt.leaf_rotation <- met_amt.leaf_PCA$rotation %>% 
  as.data.frame() %>% 
  rename_with(~ str_c("leaf_", .x)) %>%
  rownames_to_column("metabolite")

met_amt.root_rotation <- met_amt.root_PCA$rotation %>%
  as.data.frame() %>% 
  rename_with(~ str_c("root_", .x)) %>%
  rownames_to_column("metabolite")

met_amt.PC_rotation <- full_join(met_amt.leaf_rotation, met_amt.root_rotation, by="metabolite")
met_per_mg_fit1LOO <- cv.glmnet(x=met_per_mg.PCs, y=leaflength$leaf_avg_std, nfolds = nrow(met_per_mg.PCs), alpha=1 )
Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per fold
plot(met_per_mg_fit1LOO)

bestlam=met_per_mg_fit1LOO$lambda.1se

NEXT STEP: Do a K-fold CV, repeat many times and average. Might as well do alpha while we are at it. If we are doing alpha, then we need to manually create our own folds list for each run

normalized

multi CV

Fit 101 CVs for each of 11 alphas

set.seed(1245)

folds <- tibble(run=1:101) %>% 
  mutate(folds=map(run, ~ sample(rep(1:4,6))))

system.time (met_per_mg_multiCV <- expand_grid(run=1:100, alpha=round(seq(0,1,.1),1)) %>%
               left_join(folds, by="run") %>%
               mutate(fit=map2(folds, alpha, ~ cv.glmnet(x=met_per_mg.PCs, y=leaflength$leaf_avg_std, foldid = .x, alpha=.y
                                                         )))
             #, lambda=exp(seq(-5,0,length.out = 50)) )))
) #100 seconds
   user  system elapsed 
 65.095   2.523  97.788 
head(met_per_mg_multiCV)

for each fit, pull out the mean cv error, lambda, min lambda, and 1se lambda

met_per_mg_multiCV <- met_per_mg_multiCV %>%
  mutate(cvm=map(fit, magrittr::extract("cvm")),
         lambda=map(fit, magrittr::extract("lambda")),
         lambda.min=map_dbl(fit, magrittr::extract("lambda.min" )),
         lambda.1se=map_dbl(fit, magrittr::extract("lambda.1se")),
         nzero=map(fit, magrittr::extract("nzero"))
  )

head(met_per_mg_multiCV)

now calculate the mean and sem of cvm and min,1se labmdas. These need to be done separately because of the way the grouping works

met_per_mg_summary_cvm <- met_per_mg_multiCV %>% dplyr::select(-fit, -folds) %>% 
  unnest(c(cvm, lambda)) %>%
  group_by(alpha, lambda) %>%
  summarize(meancvm=mean(cvm), sem=sd(cvm)/sqrt(n()), high=meancvm+sem, low=meancvm-sem)
`summarise()` has grouped output by 'alpha'. You can override using the `.groups` argument.
met_per_mg_summary_cvm
met_per_mg_summary_lambda <- met_per_mg_multiCV %>% dplyr::select(-fit, -folds, -cvm) %>% 
  group_by(alpha) %>%
  summarize(
    lambda.min.sd=sd(lambda.min), 
    lambda.min.mean=mean(lambda.min),
    #lambda.min.med=median(lambda.min), 
    lambda.min.high=lambda.min.mean+lambda.min.sd,
    #lambda.min.low=lambda.min.mean-lambda.min.sem,
    #lambda.1se.sem=sd(lambda.1se)/sqrt(n()), 
    lambda.1se.mean=mean(lambda.1se),
    #lambda.1se.med=median(lambda.1se), 
    #lambda.1se.high=lambda.1se+lambda.1se.sem,
    #lambda.1se.low=lambda.1se-lambda.1se.sem,
    nzero=nzero[1],
    lambda=lambda[1]
  )

met_per_mg_summary_lambda

plot it

met_per_mg_summary_cvm %>%
  #filter(alpha!=0) %>% # worse than everything else and throwing the plots off
  ggplot(aes(x=log(lambda), y= meancvm,  ymin=low, ymax=high)) +
  geom_ribbon(alpha=.25) +
  geom_line(aes(color=as.character(alpha))) +
  facet_wrap(~ as.character(alpha)) +
   coord_cartesian(xlim=(c(-5,0))) +
  geom_vline(aes(xintercept=log(lambda.min.mean)), alpha=.5, data=met_per_mg_summary_lambda) +
  geom_vline(aes(xintercept=log(lambda.min.high)), alpha=.5, data=met_per_mg_summary_lambda, color="blue") 

So overall these look more reasonable than the LOO plot.

Make a plot of MSE at minimum lambda for each alpha

met_per_mg_summary_cvm %>% 
  group_by(alpha) %>%
  filter(rank(meancvm, ties.method = "first")==1) %>%
  ggplot(aes(x=alpha,y=meancvm,ymin=low,ymax=high)) +
  geom_ribbon(color=NA, fill="gray80") +
  geom_line() +
  geom_point()

not a particular large difference there, aside from 0.1 and even then, not too much better.

Plot the number of nzero coefficients

met_per_mg_summary_lambda %>%
  unnest(c(lambda, nzero)) %>%
  group_by(alpha) %>%
  filter(abs(lambda.min.mean-lambda)==min(abs(lambda.min.mean-lambda))  ) %>%
  ungroup() %>%

ggplot(aes(x=as.character(alpha), y=nzero)) +
  geom_point() +
  ggtitle("Number of non-zero coefficents at minimum lambda") +
  ylim(0,24)

OK let’s do repeated test train starting from these CV lambdas

multi_tt <- function(lambda, alpha, n=10000, sample_size=24, train_size=20, x, y=leaflength$leaf_avg_std) {
  print(lambda)
  print(alpha)
tt <-
  tibble(run=1:n) %>%
  mutate(train=map(run, ~ sample(1:sample_size, train_size))) %>%
  mutate(fit=map(train, ~ glmnet(x=x[.,], y=y[.], lambda = lambda, alpha = alpha ))) %>%
  
  mutate(pred=map2(fit, train, ~ predict(.x, newx = x[-.y,]))) %>%
  mutate(cor=map2_dbl(pred, train, ~ cor(.x, y[-.y])  )) %>%
  mutate(MSE=map2_dbl(pred, train, ~ mean((y[-.y] - .x)^2))) %>%
  summarize(
    num_na=sum(is.na(cor)), 
    num_lt_0=sum(cor<=0, na.rm=TRUE),
    avg_cor=mean(cor, na.rm=TRUE),
    avg_MSE=mean(MSE))
tt
}

per_mg_fit_test_train <- met_per_mg_summary_lambda %>% 
  select(alpha, lambda.min.mean)

per_mg_fit_test_train <- met_per_mg_multiCV %>%
  filter(run==1) %>%
  select(alpha, fit) %>%
  right_join(per_mg_fit_test_train)
Joining, by = "alpha"
per_mg_fit_test_train <- per_mg_fit_test_train %>%
  mutate(pred_full=map2(fit, lambda.min.mean, ~ predict(.x, s=.y, newx=met_per_mg.PCs)),
         full_R=map_dbl(pred_full, ~ cor(.x, leaflength$leaf_avg_std)),
         full_MSE=map_dbl(pred_full, ~ mean((leaflength$leaf_avg_std-.x)^2))) %>%
  
  mutate(tt=map2(lambda.min.mean, alpha, ~ multi_tt(lambda=.x, alpha=.y, x=met_per_mg.PCs)))
[1] 63.76959
[1] 0
[1] 3.23426
[1] 0.1
[1] 1.96389
[1] 0.2
[1] 1.390028
[1] 0.3
[1] 1.057486
[1] 0.4
[1] 0.8344099
[1] 0.5
[1] 0.7009496
[1] 0.6
[1] 0.5978767
[1] 0.7
[1] 0.527807
[1] 0.8
[1] 0.4681233
[1] 0.9
[1] 0.4171747
[1] 1
(per_mg_fit_test_train <- per_mg_fit_test_train %>% unnest(tt))
per_mg_fit_test_train %>%
  ggplot(aes(x=alpha)) +
  geom_line(aes(y=avg_cor), color="red") +
  geom_point(aes(y=avg_cor), color="red") +
  geom_line(aes(y=avg_MSE), color="blue") +
  geom_point(aes(y=avg_MSE), color="blue")

all are similar from 0.2 onwards

look at fit:

alpha_per_mg <- .2

best_per_mg <- per_mg_fit_test_train %>% filter(alpha == alpha_per_mg) 
best_per_mg_fit <- best_per_mg$fit[[1]]
best_per_mg_lambda <- best_per_mg$lambda.min.mean

per_mg_coef.tb <- coef(best_per_mg_fit, s=best_per_mg_lambda) %>% 
  as.matrix() %>% as.data.frame() %>% 
  rownames_to_column(var="PC") %>%
  rename(beta=`1`)
  
per_mg_coef.tb %>% filter(beta!=0) %>% arrange(beta)
NA

pred and obs

Percent variance explained

per_mg_vars <- per_mg_coef.tb %>% 
  filter(beta !=0, PC!="(Intercept)") %>%
  pull(PC) %>% c("leaf_avg_std", .)

per_mg_relimp <- leaflength %>% select(leaf_avg_std) %>% cbind(met_per_mg.PCs) %>% as.data.frame() %>% dplyr::select(all_of(per_mg_vars)) %>%
  calc.relimp() 

per_mg_coef.tb <- per_mg_relimp@lmg %>% as.matrix() %>% as.data.frame() %>%
  rownames_to_column("PC") %>%
  rename(PropVar_met_per_mg=V1) %>%
  full_join(per_mg_coef.tb) %>%
  arrange(desc(PropVar_met_per_mg))
Joining, by = "PC"
per_mg_coef.tb
NA

Checkout the rotations.

met_per_mg_rotation_out <- met_per_mg.PC_rotation %>% 
  pivot_longer(-metabolite, names_to="PC", values_to="loading") %>%
  filter(PC %in% filter(per_mg_coef.tb, beta!=0)$PC ) %>%
  group_by(PC) %>%
    filter(!str_detect(metabolite,".*(leaf|root)_[0-9]*$")) %>%
  filter(abs(loading) >= 0.05) %>%
  left_join(per_mg_coef.tb, by="PC") %>%
  arrange(desc(abs(beta)), desc(abs(loading))) %>%
  mutate(organ=ifelse(str_detect(metabolite, "_leaf_"), "leaf", "root"),
         transformation="normalized",
         metabolite=str_remove(metabolite, "met_per_mg_(root|leaf)_"),
         metabolite_effect_on_leaf=ifelse(beta*loading>0, "increase", "decrease"))
met_per_mg_rotation_out %>%  write_csv("../output/Leaf_associated_metabolites_normalized_NOBLANK.csv")
met_per_mg_rotation_out

non-normazlized

multi CV

Fit 101 CVs for each of 11 alphas

set.seed(1245)

folds <- tibble(run=1:101) %>% 
  mutate(folds=map(run, ~ sample(rep(1:6,4))))

system.time (met_amt_multiCV <- expand_grid(run=1:100, alpha=round(seq(0,1,.1),1)) %>%
               left_join(folds, by="run") %>%
               mutate(fit=map2(folds, alpha, ~ cv.glmnet(x=met_amt.PCs, y=leaflength$leaf_avg_std, foldid = .x, alpha=.y
                                                         )))
             #, lambda=exp(seq(-5,0,length.out = 50)) )))
) #100 seconds
   user  system elapsed 
 76.131   2.642  94.599 
head(met_amt_multiCV)

for each fit, pull out the mean cv error, lambda, min lambda, and 1se lambda

met_amt_multiCV <- met_amt_multiCV %>%
  mutate(cvm=map(fit, magrittr::extract("cvm")),
         lambda=map(fit, magrittr::extract("lambda")),
         lambda.min=map_dbl(fit, magrittr::extract("lambda.min" )),
         lambda.1se=map_dbl(fit, magrittr::extract("lambda.1se")),
         nzero=map(fit, magrittr::extract("nzero"))
  )

head(met_amt_multiCV)

now calculate the mean and sem of cvm and min,1se labmdas. These need to be done separately because of the way the grouping works

met_amt_summary_cvm <- met_amt_multiCV %>% dplyr::select(-fit, -folds) %>% 
  unnest(c(cvm, lambda)) %>%
  group_by(alpha, lambda) %>%
  summarize(meancvm=mean(cvm), sem=sd(cvm)/sqrt(n()), high=meancvm+sem, low=meancvm-sem)
`summarise()` has grouped output by 'alpha'. You can override using the `.groups` argument.
met_amt_summary_cvm
met_amt_summary_lambda <- met_amt_multiCV %>% dplyr::select(-fit, -folds, -cvm) %>% 
  group_by(alpha) %>%
  summarize(
    lambda.min.sd=sd(lambda.min), 
    lambda.min.mean=mean(lambda.min),
    #lambda.min.med=median(lambda.min), 
    lambda.min.high=lambda.min.mean+lambda.min.sd,
    #lambda.min.low=lambda.min.mean-lambda.min.sem,
    #lambda.1se.sem=sd(lambda.1se)/sqrt(n()), 
    lambda.1se.mean=mean(lambda.1se),
    #lambda.1se.med=median(lambda.1se), 
    #lambda.1se.high=lambda.1se+lambda.1se.sem,
    #lambda.1se.low=lambda.1se-lambda.1se.sem,
    nzero=nzero[1],
    lambda=lambda[1]
  )

met_amt_summary_lambda

plot it

met_amt_summary_cvm %>%
  #filter(alpha!=0) %>% # worse than everything else and throwing the plots off
  ggplot(aes(x=log(lambda), y= meancvm,  ymin=low, ymax=high)) +
  geom_ribbon(alpha=.25) +
  geom_line(aes(color=as.character(alpha))) +
  facet_wrap(~ as.character(alpha)) +
   coord_cartesian(xlim=(c(-5,0))) +
  geom_vline(aes(xintercept=log(lambda.min.mean)), alpha=.5, data=met_amt_summary_lambda) +
  geom_vline(aes(xintercept=log(lambda.min.high)), alpha=.5, data=met_amt_summary_lambda, color="blue") 

Make a plot of MSE at minimum lambda for each alpha

met_amt_summary_cvm %>% 
  group_by(alpha) %>%
  filter(rank(meancvm, ties.method = "first")==1) %>%
  ggplot(aes(x=alpha,y=meancvm,ymin=low,ymax=high)) +
  geom_ribbon(color=NA, fill="gray80") +
  geom_line() +
  geom_point()

decreasing throughout, although differences are pretty small

Plot the number of nzero coefficients

met_amt_summary_lambda %>%
  unnest(c(lambda, nzero)) %>%
  group_by(alpha) %>%
  filter(abs(lambda.min.mean-lambda)==min(abs(lambda.min.mean-lambda))  ) %>%
  ungroup() %>%

ggplot(aes(x=as.character(alpha), y=nzero)) +
  geom_point() +
  ggtitle("Number of non-zero coefficents at minimum lambda") +
  ylim(0,36)

OK let’s do repeated test train starting from these CV lambdas

multi_tt <- function(lambda, alpha, n=10000, sample_size=24, train_size=20, x, y=leaflength$leaf_avg_std) {
  print(lambda)
  print(alpha)
tt <-
  tibble(run=1:n) %>%
  mutate(train=map(run, ~ sample(1:sample_size, train_size))) %>%
  mutate(fit=map(train, ~ glmnet(x=x[.,], y=y[.], lambda = lambda, alpha = alpha ))) %>%
  
  mutate(pred=map2(fit, train, ~ predict(.x, newx = x[-.y,]))) %>%
  mutate(cor=map2_dbl(pred, train, ~ cor(.x, y[-.y])  )) %>%
  mutate(MSE=map2_dbl(pred, train, ~ mean((y[-.y] - .x)^2))) %>%
  summarize(
    num_na=sum(is.na(cor)), 
    num_lt_0=sum(cor<=0, na.rm=TRUE),
    avg_cor=mean(cor, na.rm=TRUE),
    avg_MSE=mean(MSE))
tt
}

amt_fit_test_train <- met_amt_summary_lambda %>% 
  select(alpha, lambda.min.mean)

amt_fit_test_train <- met_amt_multiCV %>%
  filter(run==1) %>%
  select(alpha, fit) %>%
  right_join(amt_fit_test_train)
Joining, by = "alpha"
amt_fit_test_train <- amt_fit_test_train %>%
  mutate(pred_full=map2(fit, lambda.min.mean, ~ predict(.x, s=.y, newx=met_amt.PCs)),
         full_R=map_dbl(pred_full, ~ cor(.x, leaflength$leaf_avg_std)),
         full_MSE=map_dbl(pred_full, ~ mean((leaflength$leaf_avg_std-.x)^2))) %>%
  
  mutate(tt=map2(lambda.min.mean, alpha, ~ multi_tt(lambda=.x, alpha=.y, x=met_amt.PCs)))
[1] 129.3889
[1] 0
[1] 2.394178
[1] 0.1
[1] 1.622926
[1] 0.2
[1] 1.129161
[1] 0.3
[1] 0.8782994
[1] 0.4
[1] 0.709034
[1] 0.5
[1] 0.5979773
[1] 0.6
[1] 0.5183774
[1] 0.7
[1] 0.4567253
[1] 0.8
[1] 0.4084716
[1] 0.9
[1] 0.3686524
[1] 1
(amt_fit_test_train <- amt_fit_test_train %>% unnest(tt))
amt_fit_test_train %>%
  ggplot(aes(x=alpha)) +
  geom_line(aes(y=avg_cor), color="red") +
  geom_point(aes(y=avg_cor), color="red") +
  geom_line(aes(y=avg_MSE), color="blue") +
  geom_point(aes(y=avg_MSE), color="blue")

not a big difference after 0.2

look at fit:

alpha_amt <- .7

best_amt <- amt_fit_test_train %>% filter(alpha == alpha_amt) 
best_amt_fit <- best_amt$fit[[1]]
best_amt_lambda <- best_amt$lambda.min.mean

amt_coef.tb <- coef(best_amt_fit, s=best_amt_lambda) %>% 
  as.matrix() %>% as.data.frame() %>% 
  rownames_to_column(var="PC") %>%
  rename(beta=`1`)
  
amt_coef.tb %>% filter(beta!=0) %>% arrange(beta)
NA

pred and obs

plot(leaflength$leaf_avg_std, best_amt$pred_full[[1]])

cor.test(leaflength$leaf_avg_std, best_amt$pred_full[[1]]) #.64

    Pearson's product-moment correlation

data:  leaflength$leaf_avg_std and best_amt$pred_full[[1]]
t = 3.9359, df = 22, p-value = 0.000705
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.3232239 0.8307777
sample estimates:
      cor 
0.6428067 
best_amt$full_MSE
[1] 0.6804825

Percent variance explained

amt_vars <- amt_coef.tb %>% 
  filter(beta !=0, PC!="(Intercept)") %>%
  pull(PC) %>% c("leaf_avg_std", .)

# because there is only one explanatory variable, we don't need to (and can't) use relimp
amt_relimp <- leaflength %>% select(leaf_avg_std) %>% cbind(met_amt.PCs) %>% as.data.frame() %>% dplyr::select(all_of(amt_vars)) %>% cor() %>% magrittr::extract(1,2) %>% magrittr::multiply_by(.,.)
amt_relimp <- tibble(PC=str_subset(amt_vars, "PC"),
                     PropVar_met_amt=amt_relimp)


amt_coef.tb <- amt_relimp %>% 
  full_join(amt_coef.tb) %>%
  arrange(desc(PropVar_met_amt))
Joining, by = "PC"
amt_coef.tb
NA

Checkout the rotations.

met_amt_rotation_out <- met_amt.PC_rotation %>% 
  pivot_longer(-metabolite, names_to="PC", values_to="loading") %>%
  filter(PC %in% filter(amt_coef.tb, beta!=0)$PC ) %>%
  group_by(PC) %>%
    filter(!str_detect(metabolite,".*(leaf|root)_[0-9]*$")) %>%
  filter(abs(loading) >= 0.05) %>%
  left_join(amt_coef.tb, by="PC") %>%
  arrange(desc(abs(beta)), desc(abs(loading))) %>%
  mutate(organ=ifelse(str_detect(metabolite, "_leaf_"), "leaf", "root"),
         transformation="raw",
         metabolite=str_remove(metabolite, "met_amt_(root|leaf)_"),
         metabolite_effect_on_leaf=ifelse(beta*loading>0, "increase", "decrease"))
met_amt_rotation_out %>%  write_csv("../output/Leaf_associated_metabolites_raw_NOBLANK.csv")

met_amt_rotation_out
LS0tCnRpdGxlOiAiTWFjaGluZSBsZWFybmluZyBmb3IgbWV0YWJvbGl0ZXMsIG5vIGJsYW5rLCBhbGwgbWV0YWJvbGl0ZXMiCmF1dGhvcjogIkp1bGluIE1hbG9vZiIKZGF0ZTogIjEvMjgvMjAyMSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgpHb2FsOiBjYW4gbWFjaGluZSBsZWFybmluZyBtZXRob2RzIGhlbHAgdXMgdG8gYXNzb2NpYXRlIG1ldGFib2xpdGVzIHdpdGggbGVhZiBsZW5ndGg/CgpQcmV2aW91c2x5IChzY3JpcHQgMTFiMikgSSBmaWx0ZXJlZCBvdXQgdW5uYW1lZCBtZXRhYm9saXRlcy4gIEhlcmUgSSBrZWVwIHRoZW0gYWxsLiAgYnV0IHJlbW92ZSBibGFuayBzYW1wbGVzCgpgYGB7cn0KbGlicmFyeShnbG1uZXQpCmxpYnJhcnkocmVsYWltcG8pCmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCmdldCBsZWFmbGVuZ3RoIGRhdGEKYGBge3J9CmxlYWZsZW5ndGggPC0gcmVhZF9jc3YoIi4uLy4uL3BsYW50L291dHB1dC9sZWFmX2xlbmd0aHNfbWV0YWJvbGl0ZS5jc3YiKSAlPiUKICBtdXRhdGUocG90PXN0cl9wYWQocG90LCB3aWR0aD0zLCBwYWQ9IjAiKSwKICAgICAgICAgc2FtcGxlSUQ9c3RyX2MoInd5byIsIGdlbm90eXBlLCBwb3QsIHNlcD0iXyIpLCAKICAgICAgICAgZ3JvdXA9c3RyX2Moc29pbCxnZW5vdHlwZSx0cnQsc2VwPSJfIikpICU+JQogIHNlbGVjdChzYW1wbGVJRCwgZ3JvdXAsIGdlbm90eXBlLCB0cnQsIGxlYWZfYXZnX3N0ZCkgICU+JQogIGZpbHRlcighc3RyX2RldGVjdChncm91cCwgIkJMQU5LIikpCmxlbmd0aCh1bmlxdWUobGVhZmxlbmd0aCRncm91cCkpCmxlYWZsZW5ndGggJT4lIGFycmFuZ2Uoc2FtcGxlSUQpCmBgYAoKZ2V0IGFuZCB3cmFuZ2xlIG1ldGFib2xpdGUgZGF0YQpgYGB7cn0KbWV0X3JhdyA8LXJlYWRfY3N2KCIuLi9pbnB1dC9tZXRhYm9saXRlc19zZXQxLmNzdiIpCm1ldCA8LSBtZXRfcmF3ICU+JSAKICBtdXRhdGUocG90PXN0cl9wYWQocG90LCB3aWR0aCA9IDMsIHBhZCA9ICIwIikpICU+JQogIG11dGF0ZShzYW1wbGVJRD1zdHJfYygid3lvIiwgZ2Vub3R5cGUsIHBvdCwgc2VwPSJfIikpICU+JQogIGZpbHRlcihzb2lsIT0iQkxBTksiKSAlPiUKICBzZWxlY3Qoc2FtcGxlSUQsIGdlbm90eXBlLCB0aXNzdWUsIHNhbXBsZV9tYXNzID0gYHNhbXBsZV9tYXNzIG1nYCwgIXN1Ym1pc3Npb25fbnVtYmVyOmNvbmNhdGVuYXRlKSAlPiUKICBwaXZvdF9sb25nZXIoIXNhbXBsZUlEOnNhbXBsZV9tYXNzLCBuYW1lc190byA9ICJtZXRhYm9saXRlIiwgdmFsdWVzX3RvID0gIm1ldF9hbW91bnQiKSAlPiUKICAKICAjYWRqdXN0IGJ5IHNhbXBsZSBtYXNzCiAgbXV0YXRlKG1ldF9wZXJfbWc9bWV0X2Ftb3VudC9zYW1wbGVfbWFzcykgJT4lCiAgCiAgI3NjYWxlIGFuZCBjZW50ZXIKICBncm91cF9ieShtZXRhYm9saXRlLCBnZW5vdHlwZSwgdGlzc3VlKSAlPiUKICBtdXRhdGUobWV0X3Blcl9tZz1zY2FsZShtZXRfcGVyX21nKSwKICAgICAgICAgbWV0X2FtdD1zY2FsZShtZXRfYW1vdW50KQogICkgJT4lIAogIHBpdm90X3dpZGVyKGlkX2NvbHMgPSBzYW1wbGVJRCwgCiAgICAgICAgICAgICAgbmFtZXNfZnJvbSA9IGModGlzc3VlLCBtZXRhYm9saXRlKSwgCiAgICAgICAgICAgICAgdmFsdWVzX2Zyb20gPSBzdGFydHNfd2l0aCgibWV0XyIpLAogICAgICAgICAgICAgIG5hbWVzX3NlcCA9ICJfIikKCm1ldApgYGAKCnNwbGl0IHRoaXMgaW50byB0d28gZGF0YSBmcmFtZXMsIG9uZSBub3JtYWxpemVkIGJ5IHRpc3N1ZSBhbW91bnQgYW5kIG9uZSBub3QuCmBgYHtyfQptZXRfcGVyX21nIDwtIG1ldCAlPiUgc2VsZWN0KHNhbXBsZUlELCAgc3RhcnRzX3dpdGgoIm1ldF9wZXJfbWciKSkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoInNhbXBsZUlEIikKbWV0X2FtdCA8LSBtZXQgJT4lIHNlbGVjdChzYW1wbGVJRCwgIHN0YXJ0c193aXRoKCJtZXRfYW10IikpICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCJzYW1wbGVJRCIpCmBgYAoKZ2V0IGxlYWYgZGF0YSBvcmRlciB0byBtYXRjaAoKYGBge3J9CmxlYWZsZW5ndGggPC0gbGVhZmxlbmd0aFttYXRjaChtZXQkc2FtcGxlSUQsIGxlYWZsZW5ndGgkc2FtcGxlSUQpLF0KbGVhZmxlbmd0aApgYGAKCgojIyBDYWxjIFBDQXM6CgojIyMgbm9ybWFsaXplZAoKIyMjIyBsZWFmCgpgYGB7cn0KbWV0X3Blcl9tZy5sZWFmX1BDQSA8LSBtZXRfcGVyX21nICU+JSAKICBzZWxlY3QobWF0Y2hlcygiX2xlYWZfIikpICU+JQogIHByY29tcChjZW50ZXIgPSBGQUxTRSwgc2NhbGUuID0gRkFMU0UpICNhbHJlYWR5IGNlbnRlcmVkIGFuZCBzY2FsZWQKbmFtZXMobWV0X3Blcl9tZy5sZWFmX1BDQSkKdGliYmxlKHZhcmlhbmNlPW1ldF9wZXJfbWcubGVhZl9QQ0Ekc2Rldl4yLCBQQz1zdHJfYygiUEMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX3BhZCgxOmxlbmd0aChtZXRfcGVyX21nLmxlYWZfUENBJHNkZXYpLCB3aWR0aCA9IDIsIHBhZD0iMCIpKSkgJT4lCiAgbXV0YXRlKHBlcmNlbnRfdmFyPTEwMCp2YXJpYW5jZS9zdW0odmFyaWFuY2UpLCAgCiAgICAgICAgIGN1bXVsYXRpdmVfdmFyPWN1bXN1bShwZXJjZW50X3ZhcikpICU+JQogIG1hZ3JpdHRyOjpleHRyYWN0KDE6MTUsKSAlPiUKICBnZ3Bsb3QoYWVzKHg9UEMsIHk9cGVyY2VudF92YXIpKSArCiAgZ2VvbV9jb2woZmlsbD0ic2t5Ymx1ZSIpICsgCiAgZ2VvbV9saW5lKGFlcyh5PWN1bXVsYXRpdmVfdmFyKSwgZ3JvdXA9IiIpICsKICBnZ3RpdGxlKCJwZXJjZW50IHZhcmlhbmNlIGV4cGxhaW5lZCwgbmFtZWQsIG5vcm1hbGl6ZWQgbGVhZiBtZXRhYm9saXRlcyIpCmBgYAoKCiMjIHNvbWUgdC50ZXN0cwoKYGBge3J9Cm1ldF9wZXJfbWcubGVhZl9QQ0EkeCAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJzYW1wbGVJRCIpICU+JQogIGxlZnRfam9pbihsZWFmbGVuZ3RoKSAlPiUKICBzZWxlY3Qoc2FtcGxlSUQsIGdlbm90eXBlLCB0cnQsIHN0YXJ0c193aXRoKCJQQyIpKSAlPiUKICBzdW1tYXJpc2UoYWNyb3NzKHN0YXJ0c193aXRoKCJQQyIpLCB+IHQudGVzdCgueCB+IHRydCkkcC52YWx1ZSkpICU+JQogIHBpdm90X2xvbmdlcihzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgYXJyYW5nZSh2YWx1ZSkKYGBgCgoKCiMjIyMgcm9vdAoKYGBge3J9Cm1ldF9wZXJfbWcucm9vdF9QQ0EgPC0gbWV0X3Blcl9tZyAlPiUgCiAgc2VsZWN0KG1hdGNoZXMoIl9yb290XyIpKSAlPiUKICBwcmNvbXAoY2VudGVyID0gRkFMU0UsIHNjYWxlLiA9IEZBTFNFKSAjYWxyZWFkeSBjZW50ZXJlZCBhbmQgc2NhbGVkCm5hbWVzKG1ldF9wZXJfbWcucm9vdF9QQ0EpCnRpYmJsZSh2YXJpYW5jZT1tZXRfcGVyX21nLnJvb3RfUENBJHNkZXZeMiwgUEM9c3RyX2MoIlBDIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cl9wYWQoMTpsZW5ndGgobWV0X3Blcl9tZy5yb290X1BDQSRzZGV2KSwgd2lkdGggPSAyLCBwYWQ9IjAiKSkpICU+JQogIG11dGF0ZShwZXJjZW50X3Zhcj0xMDAqdmFyaWFuY2Uvc3VtKHZhcmlhbmNlKSwgIAogICAgICAgICBjdW11bGF0aXZlX3Zhcj1jdW1zdW0ocGVyY2VudF92YXIpKSAlPiUKICBtYWdyaXR0cjo6ZXh0cmFjdCgxOjE1LCkgJT4lCiAgZ2dwbG90KGFlcyh4PVBDLCB5PXBlcmNlbnRfdmFyKSkgKwogIGdlb21fY29sKGZpbGw9InNreWJsdWUiKSArIAogIGdlb21fbGluZShhZXMoeT1jdW11bGF0aXZlX3ZhciksIGdyb3VwPSIiKSArCiAgZ2d0aXRsZSgicGVyY2VudCB2YXJpYW5jZSBleHBsYWluZWQsIG5hbWVkLCBub3JtYWxpemVkIHJvb3QgbWV0YWJvbGl0ZXMiKQpgYGAKCmBgYHtyfQptZXRfcGVyX21nLnJvb3RfUENBJHggJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlSUQiKSAlPiUKICBsZWZ0X2pvaW4obGVhZmxlbmd0aCkgJT4lCiAgc2VsZWN0KHNhbXBsZUlELCBnZW5vdHlwZSwgdHJ0LCBzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcyhzdGFydHNfd2l0aCgiUEMiKSwgfiB0LnRlc3QoLnggfiB0cnQpJHAudmFsdWUpKSAlPiUKICBwaXZvdF9sb25nZXIoc3RhcnRzX3dpdGgoIlBDIikpICU+JQogIGFycmFuZ2UodmFsdWUpCmBgYAoKUEMyIGFuZCBtYXJnaW5hbGx5IFBDNSAKCmBgYHtyfQptZXRfcGVyX21nLnJvb3RfUENBJHggJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlSUQiKSAlPiUKICBsZWZ0X2pvaW4obGVhZmxlbmd0aCkgJT4lCiAgc2VsZWN0KHNhbXBsZUlELCBnZW5vdHlwZSwgdHJ0LCBzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcyhzdGFydHNfd2l0aCgiUEMiKSwgfiB0LnRlc3QoLnggfiBnZW5vdHlwZSkkcC52YWx1ZSkpICU+JQogIHBpdm90X2xvbmdlcihzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgYXJyYW5nZSh2YWx1ZSkKYGBgCgojIyMgcmF3CgojIyMjIGxlYWYKYGBge3J9Cm1ldF9hbXQubGVhZl9QQ0EgPC0gbWV0X2FtdCAlPiUKICBzZWxlY3QobWF0Y2hlcygiX2xlYWZfIikpICU+JQogIHByY29tcChjZW50ZXIgPSBGQUxTRSwgc2NhbGUuID0gRkFMU0UpICNhbHJlYWR5IGNlbnRlcmVkIGFuZCBzY2FsZWQKbmFtZXMobWV0X3Blcl9tZy5sZWFmX1BDQSkKdGliYmxlKHZhcmlhbmNlPW1ldF9hbXQubGVhZl9QQ0Ekc2Rldl4yLCBQQz1zdHJfYygiUEMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX3BhZCgxOmxlbmd0aChtZXRfYW10LmxlYWZfUENBJHNkZXYpLCB3aWR0aCA9IDIsIHBhZD0iMCIpKSkgJT4lCiAgbXV0YXRlKHBlcmNlbnRfdmFyPTEwMCp2YXJpYW5jZS9zdW0odmFyaWFuY2UpLCAgCiAgICAgICAgIGN1bXVsYXRpdmVfdmFyPWN1bXN1bShwZXJjZW50X3ZhcikpICU+JQogIG1hZ3JpdHRyOjpleHRyYWN0KDE6MTUsKSAlPiUKICBnZ3Bsb3QoYWVzKHg9UEMsIHk9cGVyY2VudF92YXIpKSArCiAgZ2VvbV9jb2woZmlsbD0ic2t5Ymx1ZSIpICsgCiAgZ2VvbV9saW5lKGFlcyh5PWN1bXVsYXRpdmVfdmFyKSwgZ3JvdXA9IiIpICsKICBnZ3RpdGxlKCJwZXJjZW50IHZhcmlhbmNlIGV4cGxhaW5lZCwgbmFtZWQsIHJhdyBsZWFmIG1ldGFib2xpdGVzIikKYGBgCgp0LnRlc3RzCgpgYGB7cn0KbWV0X2FtdC5sZWFmX1BDQSR4ICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICByb3duYW1lc190b19jb2x1bW4oInNhbXBsZUlEIikgJT4lCiAgbGVmdF9qb2luKGxlYWZsZW5ndGgpICU+JQogIHNlbGVjdChzYW1wbGVJRCwgZ2Vub3R5cGUsIHRydCwgc3RhcnRzX3dpdGgoIlBDIikpICU+JQogIHN1bW1hcmlzZShhY3Jvc3Moc3RhcnRzX3dpdGgoIlBDIiksIH4gdC50ZXN0KC54IH4gdHJ0KSRwLnZhbHVlKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKHN0YXJ0c193aXRoKCJQQyIpKSAlPiUKICBhcnJhbmdlKHZhbHVlKQpgYGAKCmBgYHtyfQptZXRfYW10LmxlYWZfUENBJHggJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlSUQiKSAlPiUKICBsZWZ0X2pvaW4obGVhZmxlbmd0aCkgJT4lCiAgc2VsZWN0KHNhbXBsZUlELCBnZW5vdHlwZSwgdHJ0LCBzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcyhzdGFydHNfd2l0aCgiUEMiKSwgfiB0LnRlc3QoLnggfiBnZW5vdHlwZSkkcC52YWx1ZSkpICU+JQogIHBpdm90X2xvbmdlcihzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgYXJyYW5nZSh2YWx1ZSkKYGBgCgojIyMjIHJvb3QKYGBge3IsIGZpZy5hc3A9MX0KbWV0X2FtdC5yb290X1BDQSA8LSBtZXRfYW10ICU+JQogIHNlbGVjdChtYXRjaGVzKCJfcm9vdF8iKSkgJT4lCiAgcHJjb21wKGNlbnRlciA9IEZBTFNFLCBzY2FsZS4gPSBGQUxTRSkgI2FscmVhZHkgY2VudGVyZWQgYW5kIHNjYWxlZApuYW1lcyhtZXRfcGVyX21nLnJvb3RfUENBKQp0aWJibGUodmFyaWFuY2U9bWV0X2FtdC5yb290X1BDQSRzZGV2XjIsIFBDPXN0cl9jKCJQQyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfcGFkKDE6bGVuZ3RoKG1ldF9hbXQucm9vdF9QQ0Ekc2RldiksIHdpZHRoID0gMiwgcGFkPSIwIikpKSAlPiUKICBtdXRhdGUocGVyY2VudF92YXI9MTAwKnZhcmlhbmNlL3N1bSh2YXJpYW5jZSksICAKICAgICAgICAgY3VtdWxhdGl2ZV92YXI9Y3Vtc3VtKHBlcmNlbnRfdmFyKSkgJT4lCiAgbWFncml0dHI6OmV4dHJhY3QoMToxNSwpICU+JQogIGdncGxvdChhZXMoeD1QQywgeT1wZXJjZW50X3ZhcikpICsKICBnZW9tX2NvbChmaWxsPSJza3libHVlIikgKyAKICBnZW9tX2xpbmUoYWVzKHk9Y3VtdWxhdGl2ZV92YXIpLCBncm91cD0iIikgKwogIGdndGl0bGUoInBlcmNlbnQgdmFyaWFuY2UgZXhwbGFpbmVkLCBuYW1lZCwgcmF3IHJvb3QgbWV0YWJvbGl0ZXMiKQpgYGAKCnQudGVzdApgYGB7cn0KbWV0X2FtdC5yb290X1BDQSR4ICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICByb3duYW1lc190b19jb2x1bW4oInNhbXBsZUlEIikgJT4lCiAgbGVmdF9qb2luKGxlYWZsZW5ndGgpICU+JQogIHNlbGVjdChzYW1wbGVJRCwgZ2Vub3R5cGUsIHRydCwgc3RhcnRzX3dpdGgoIlBDIikpICU+JQogIHN1bW1hcmlzZShhY3Jvc3Moc3RhcnRzX3dpdGgoIlBDIiksIH4gdC50ZXN0KC54IH4gdHJ0KSRwLnZhbHVlKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKHN0YXJ0c193aXRoKCJQQyIpKSAlPiUKICBhcnJhbmdlKHZhbHVlKQpgYGAKCmBgYHtyfQptZXRfYW10LnJvb3RfUENBJHggJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlSUQiKSAlPiUKICBsZWZ0X2pvaW4obGVhZmxlbmd0aCkgJT4lCiAgc2VsZWN0KHNhbXBsZUlELCBnZW5vdHlwZSwgdHJ0LCBzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcyhzdGFydHNfd2l0aCgiUEMiKSwgfiB0LnRlc3QoLnggfiBnZW5vdHlwZSkkcC52YWx1ZSkpICU+JQogIHBpdm90X2xvbmdlcihzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgYXJyYW5nZSh2YWx1ZSkKYGBgCgoKIyMgbm93IHRyeSB0aGVzZSBpbiBhIHBlbmFsaXplZCByZWdyZXNzaW9uCgojIyMgbm9ybWFsaXplZAoKYXJlIHRoZSBQQ3Mgbm9ybWFsaXplZD8KYGBge3J9CmNvbE1lYW5zKG1ldF9hbXQubGVhZl9QQ0EkeCkgJT4lIHJvdW5kKDMpICN5ZXMgY2VudGVyZWQKYXBwbHkobWV0X2FtdC5sZWFmX1BDQSR4LCAyLCBzZCkgJT4lIHJvdW5kKDIpICNub3Qgc2NhbGVkCmBgYAoKY29tYmluZSB0aGUgbGVhZiBhbmQgcm9vdCwgYW5kIHRoZW4gc2NhbGUgdGhlbToKYGBge3J9Cm1ldF9wZXJfbWcubGVhZl9QQ3MgPC0gbWV0X3Blcl9tZy5sZWFmX1BDQSR4CmNvbG5hbWVzKG1ldF9wZXJfbWcubGVhZl9QQ3MpIDwtIHN0cl9jKCJsZWFmXyIsIGNvbG5hbWVzKG1ldF9wZXJfbWcubGVhZl9QQ3MpKQoKbWV0X3Blcl9tZy5yb290X1BDcyA8LSBtZXRfcGVyX21nLnJvb3RfUENBJHgKY29sbmFtZXMobWV0X3Blcl9tZy5yb290X1BDcykgPC0gc3RyX2MoInJvb3RfIiwgY29sbmFtZXMobWV0X3Blcl9tZy5yb290X1BDcykpCgptZXRfcGVyX21nLlBDcyA8LSBjYmluZChtZXRfcGVyX21nLmxlYWZfUENzLCBtZXRfcGVyX21nLnJvb3RfUENzKSAlPiUKICBzY2FsZSgpCgptZXRfYW10LmxlYWZfUENzIDwtIG1ldF9hbXQubGVhZl9QQ0EkeApjb2xuYW1lcyhtZXRfYW10LmxlYWZfUENzKSA8LSBzdHJfYygibGVhZl8iLCBjb2xuYW1lcyhtZXRfYW10LmxlYWZfUENzKSkKCm1ldF9hbXQucm9vdF9QQ3MgPC0gbWV0X2FtdC5yb290X1BDQSR4CmNvbG5hbWVzKG1ldF9hbXQucm9vdF9QQ3MpIDwtIHN0cl9jKCJyb290XyIsIGNvbG5hbWVzKG1ldF9hbXQucm9vdF9QQ3MpKQoKbWV0X2FtdC5QQ3MgPC0gY2JpbmQobWV0X2FtdC5sZWFmX1BDcywgbWV0X2FtdC5yb290X1BDcykgJT4lCiAgc2NhbGUoKQpgYGAKCmFsc28gY29tYmluZSB0aGUgcm90YXRpb25zCmBgYHtyfQptZXRfcGVyX21nLmxlYWZfcm90YXRpb24gPC0gbWV0X3Blcl9tZy5sZWFmX1BDQSRyb3RhdGlvbiAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIHJlbmFtZV93aXRoKH4gc3RyX2MoImxlYWZfIiwgLngpKSAlPiUKICByb3duYW1lc190b19jb2x1bW4oIm1ldGFib2xpdGUiKQoKbWV0X3Blcl9tZy5yb290X3JvdGF0aW9uIDwtIG1ldF9wZXJfbWcucm9vdF9QQ0Ekcm90YXRpb24gJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICByZW5hbWVfd2l0aCh+IHN0cl9jKCJyb290XyIsIC54KSkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJtZXRhYm9saXRlIikKCm1ldF9wZXJfbWcuUENfcm90YXRpb24gPC0gZnVsbF9qb2luKG1ldF9wZXJfbWcubGVhZl9yb3RhdGlvbiwgbWV0X3Blcl9tZy5yb290X3JvdGF0aW9uLCBieT0ibWV0YWJvbGl0ZSIpCgptZXRfYW10LmxlYWZfcm90YXRpb24gPC0gbWV0X2FtdC5sZWFmX1BDQSRyb3RhdGlvbiAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICByZW5hbWVfd2l0aCh+IHN0cl9jKCJsZWFmXyIsIC54KSkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJtZXRhYm9saXRlIikKCm1ldF9hbXQucm9vdF9yb3RhdGlvbiA8LSBtZXRfYW10LnJvb3RfUENBJHJvdGF0aW9uICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgcmVuYW1lX3dpdGgofiBzdHJfYygicm9vdF8iLCAueCkpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigibWV0YWJvbGl0ZSIpCgptZXRfYW10LlBDX3JvdGF0aW9uIDwtIGZ1bGxfam9pbihtZXRfYW10LmxlYWZfcm90YXRpb24sIG1ldF9hbXQucm9vdF9yb3RhdGlvbiwgYnk9Im1ldGFib2xpdGUiKQoKYGBgCgoKCmBgYHtyfQptZXRfcGVyX21nX2ZpdDFMT08gPC0gY3YuZ2xtbmV0KHg9bWV0X3Blcl9tZy5QQ3MsIHk9bGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQsIG5mb2xkcyA9IG5yb3cobWV0X3Blcl9tZy5QQ3MpLCBhbHBoYT0xICkKcGxvdChtZXRfcGVyX21nX2ZpdDFMT08pCmJlc3RsYW09bWV0X3Blcl9tZ19maXQxTE9PJGxhbWJkYS4xc2UKYGBgCgogIAoKTkVYVCBTVEVQOiBEbyBhIEstZm9sZCBDViwgcmVwZWF0IG1hbnkgdGltZXMgYW5kIGF2ZXJhZ2UuICBNaWdodCBhcyB3ZWxsIGRvIGFscGhhIHdoaWxlIHdlIGFyZSBhdCBpdC4gIElmIHdlIGFyZSBkb2luZyBhbHBoYSwgdGhlbiB3ZSBuZWVkIHRvIG1hbnVhbGx5IGNyZWF0ZSBvdXIgb3duIGZvbGRzIGxpc3QgZm9yIGVhY2ggcnVuCgojIG5vcm1hbGl6ZWQKCiMjIG11bHRpIENWCgpGaXQgMTAxIENWcyBmb3IgZWFjaCBvZiAxMSBhbHBoYXMKYGBge3J9CnNldC5zZWVkKDEyNDUpCgpmb2xkcyA8LSB0aWJibGUocnVuPTE6MTAxKSAlPiUgCiAgbXV0YXRlKGZvbGRzPW1hcChydW4sIH4gc2FtcGxlKHJlcCgxOjQsNikpKSkKCnN5c3RlbS50aW1lIChtZXRfcGVyX21nX211bHRpQ1YgPC0gZXhwYW5kX2dyaWQocnVuPTE6MTAwLCBhbHBoYT1yb3VuZChzZXEoMCwxLC4xKSwxKSkgJT4lCiAgICAgICAgICAgICAgIGxlZnRfam9pbihmb2xkcywgYnk9InJ1biIpICU+JQogICAgICAgICAgICAgICBtdXRhdGUoZml0PW1hcDIoZm9sZHMsIGFscGhhLCB+IGN2LmdsbW5ldCh4PW1ldF9wZXJfbWcuUENzLCB5PWxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkLCBmb2xkaWQgPSAueCwgYWxwaGE9LnkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkpCiAgICAgICAgICAgICAjLCBsYW1iZGE9ZXhwKHNlcSgtNSwwLGxlbmd0aC5vdXQgPSA1MCkpICkpKQopICMxMDAgc2Vjb25kcwoKaGVhZChtZXRfcGVyX21nX211bHRpQ1YpCmBgYAoKZm9yIGVhY2ggZml0LCBwdWxsIG91dCB0aGUgbWVhbiBjdiBlcnJvciwgbGFtYmRhLCBtaW4gbGFtYmRhLCBhbmQgMXNlIGxhbWJkYSAKYGBge3J9Cm1ldF9wZXJfbWdfbXVsdGlDViA8LSBtZXRfcGVyX21nX211bHRpQ1YgJT4lCiAgbXV0YXRlKGN2bT1tYXAoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgiY3ZtIikpLAogICAgICAgICBsYW1iZGE9bWFwKGZpdCwgbWFncml0dHI6OmV4dHJhY3QoImxhbWJkYSIpKSwKICAgICAgICAgbGFtYmRhLm1pbj1tYXBfZGJsKGZpdCwgbWFncml0dHI6OmV4dHJhY3QoImxhbWJkYS5taW4iICkpLAogICAgICAgICBsYW1iZGEuMXNlPW1hcF9kYmwoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibGFtYmRhLjFzZSIpKSwKICAgICAgICAgbnplcm89bWFwKGZpdCwgbWFncml0dHI6OmV4dHJhY3QoIm56ZXJvIikpCiAgKQoKaGVhZChtZXRfcGVyX21nX211bHRpQ1YpCmBgYAoKCm5vdyBjYWxjdWxhdGUgdGhlIG1lYW4gYW5kIHNlbSBvZiBjdm0gYW5kIG1pbiwxc2UgbGFibWRhcy4gIFRoZXNlIG5lZWQgdG8gYmUgZG9uZSBzZXBhcmF0ZWx5IGJlY2F1c2Ugb2YgdGhlIHdheSB0aGUgZ3JvdXBpbmcgd29ya3MKYGBge3J9Cm1ldF9wZXJfbWdfc3VtbWFyeV9jdm0gPC0gbWV0X3Blcl9tZ19tdWx0aUNWICU+JSBkcGx5cjo6c2VsZWN0KC1maXQsIC1mb2xkcykgJT4lIAogIHVubmVzdChjKGN2bSwgbGFtYmRhKSkgJT4lCiAgZ3JvdXBfYnkoYWxwaGEsIGxhbWJkYSkgJT4lCiAgc3VtbWFyaXplKG1lYW5jdm09bWVhbihjdm0pLCBzZW09c2QoY3ZtKS9zcXJ0KG4oKSksIGhpZ2g9bWVhbmN2bStzZW0sIGxvdz1tZWFuY3ZtLXNlbSkKCm1ldF9wZXJfbWdfc3VtbWFyeV9jdm0KYGBgCgpgYGB7cn0KbWV0X3Blcl9tZ19zdW1tYXJ5X2xhbWJkYSA8LSBtZXRfcGVyX21nX211bHRpQ1YgJT4lIGRwbHlyOjpzZWxlY3QoLWZpdCwgLWZvbGRzLCAtY3ZtKSAlPiUgCiAgZ3JvdXBfYnkoYWxwaGEpICU+JQogIHN1bW1hcml6ZSgKICAgIGxhbWJkYS5taW4uc2Q9c2QobGFtYmRhLm1pbiksIAogICAgbGFtYmRhLm1pbi5tZWFuPW1lYW4obGFtYmRhLm1pbiksCiAgICAjbGFtYmRhLm1pbi5tZWQ9bWVkaWFuKGxhbWJkYS5taW4pLCAKICAgIGxhbWJkYS5taW4uaGlnaD1sYW1iZGEubWluLm1lYW4rbGFtYmRhLm1pbi5zZCwKICAgICNsYW1iZGEubWluLmxvdz1sYW1iZGEubWluLm1lYW4tbGFtYmRhLm1pbi5zZW0sCiAgICAjbGFtYmRhLjFzZS5zZW09c2QobGFtYmRhLjFzZSkvc3FydChuKCkpLCAKICAgIGxhbWJkYS4xc2UubWVhbj1tZWFuKGxhbWJkYS4xc2UpLAogICAgI2xhbWJkYS4xc2UubWVkPW1lZGlhbihsYW1iZGEuMXNlKSwgCiAgICAjbGFtYmRhLjFzZS5oaWdoPWxhbWJkYS4xc2UrbGFtYmRhLjFzZS5zZW0sCiAgICAjbGFtYmRhLjFzZS5sb3c9bGFtYmRhLjFzZS1sYW1iZGEuMXNlLnNlbSwKICAgIG56ZXJvPW56ZXJvWzFdLAogICAgbGFtYmRhPWxhbWJkYVsxXQogICkKCm1ldF9wZXJfbWdfc3VtbWFyeV9sYW1iZGEKYGBgCgoKcGxvdCBpdApgYGB7cn0KbWV0X3Blcl9tZ19zdW1tYXJ5X2N2bSAlPiUKICAjZmlsdGVyKGFscGhhIT0wKSAlPiUgIyB3b3JzZSB0aGFuIGV2ZXJ5dGhpbmcgZWxzZSBhbmQgdGhyb3dpbmcgdGhlIHBsb3RzIG9mZgogIGdncGxvdChhZXMoeD1sb2cobGFtYmRhKSwgeT0gbWVhbmN2bSwgIHltaW49bG93LCB5bWF4PWhpZ2gpKSArCiAgZ2VvbV9yaWJib24oYWxwaGE9LjI1KSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj1hcy5jaGFyYWN0ZXIoYWxwaGEpKSkgKwogIGZhY2V0X3dyYXAofiBhcy5jaGFyYWN0ZXIoYWxwaGEpKSArCiAgIGNvb3JkX2NhcnRlc2lhbih4bGltPShjKC01LDApKSkgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bG9nKGxhbWJkYS5taW4ubWVhbikpLCBhbHBoYT0uNSwgZGF0YT1tZXRfcGVyX21nX3N1bW1hcnlfbGFtYmRhKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1sb2cobGFtYmRhLm1pbi5oaWdoKSksIGFscGhhPS41LCBkYXRhPW1ldF9wZXJfbWdfc3VtbWFyeV9sYW1iZGEsIGNvbG9yPSJibHVlIikgCgpgYGAKCgoKU28gb3ZlcmFsbCB0aGVzZSBsb29rIG1vcmUgcmVhc29uYWJsZSB0aGFuIHRoZSBMT08gcGxvdC4KCk1ha2UgYSBwbG90IG9mIE1TRSBhdCBtaW5pbXVtIGxhbWJkYSBmb3IgZWFjaCBhbHBoYQoKYGBge3J9Cm1ldF9wZXJfbWdfc3VtbWFyeV9jdm0gJT4lIAogIGdyb3VwX2J5KGFscGhhKSAlPiUKICBmaWx0ZXIocmFuayhtZWFuY3ZtLCB0aWVzLm1ldGhvZCA9ICJmaXJzdCIpPT0xKSAlPiUKICBnZ3Bsb3QoYWVzKHg9YWxwaGEseT1tZWFuY3ZtLHltaW49bG93LHltYXg9aGlnaCkpICsKICBnZW9tX3JpYmJvbihjb2xvcj1OQSwgZmlsbD0iZ3JheTgwIikgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkKYGBgCm5vdCBhIHBhcnRpY3VsYXIgbGFyZ2UgZGlmZmVyZW5jZSB0aGVyZSwgYXNpZGUgZnJvbSAwLjEgYW5kIGV2ZW4gdGhlbiwgbm90IHRvbyBtdWNoIGJldHRlci4KClBsb3QgdGhlIG51bWJlciBvZiBuemVybyBjb2VmZmljaWVudHMKCmBgYHtyfQptZXRfcGVyX21nX3N1bW1hcnlfbGFtYmRhICU+JQogIHVubmVzdChjKGxhbWJkYSwgbnplcm8pKSAlPiUKICBncm91cF9ieShhbHBoYSkgJT4lCiAgZmlsdGVyKGFicyhsYW1iZGEubWluLm1lYW4tbGFtYmRhKT09bWluKGFicyhsYW1iZGEubWluLm1lYW4tbGFtYmRhKSkgICkgJT4lCiAgdW5ncm91cCgpICU+JQoKZ2dwbG90KGFlcyh4PWFzLmNoYXJhY3RlcihhbHBoYSksIHk9bnplcm8pKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZ3RpdGxlKCJOdW1iZXIgb2Ygbm9uLXplcm8gY29lZmZpY2VudHMgYXQgbWluaW11bSBsYW1iZGEiKSArCiAgeWxpbSgwLDI0KQpgYGAKT0sgbGV0J3MgZG8gcmVwZWF0ZWQgdGVzdCB0cmFpbiBzdGFydGluZyBmcm9tIHRoZXNlIENWIGxhbWJkYXMKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQptdWx0aV90dCA8LSBmdW5jdGlvbihsYW1iZGEsIGFscGhhLCBuPTEwMDAwLCBzYW1wbGVfc2l6ZT0yNCwgdHJhaW5fc2l6ZT0yMCwgeCwgeT1sZWFmbGVuZ3RoJGxlYWZfYXZnX3N0ZCkgewogIHByaW50KGxhbWJkYSkKICBwcmludChhbHBoYSkKdHQgPC0KICB0aWJibGUocnVuPTE6bikgJT4lCiAgbXV0YXRlKHRyYWluPW1hcChydW4sIH4gc2FtcGxlKDE6c2FtcGxlX3NpemUsIHRyYWluX3NpemUpKSkgJT4lCiAgbXV0YXRlKGZpdD1tYXAodHJhaW4sIH4gZ2xtbmV0KHg9eFsuLF0sIHk9eVsuXSwgbGFtYmRhID0gbGFtYmRhLCBhbHBoYSA9IGFscGhhICkpKSAlPiUKICAKICBtdXRhdGUocHJlZD1tYXAyKGZpdCwgdHJhaW4sIH4gcHJlZGljdCgueCwgbmV3eCA9IHhbLS55LF0pKSkgJT4lCiAgbXV0YXRlKGNvcj1tYXAyX2RibChwcmVkLCB0cmFpbiwgfiBjb3IoLngsIHlbLS55XSkgICkpICU+JQogIG11dGF0ZShNU0U9bWFwMl9kYmwocHJlZCwgdHJhaW4sIH4gbWVhbigoeVstLnldIC0gLngpXjIpKSkgJT4lCiAgc3VtbWFyaXplKAogICAgbnVtX25hPXN1bShpcy5uYShjb3IpKSwgCiAgICBudW1fbHRfMD1zdW0oY29yPD0wLCBuYS5ybT1UUlVFKSwKICAgIGF2Z19jb3I9bWVhbihjb3IsIG5hLnJtPVRSVUUpLAogICAgYXZnX01TRT1tZWFuKE1TRSkpCnR0Cn0KCnBlcl9tZ19maXRfdGVzdF90cmFpbiA8LSBtZXRfcGVyX21nX3N1bW1hcnlfbGFtYmRhICU+JSAKICBzZWxlY3QoYWxwaGEsIGxhbWJkYS5taW4ubWVhbikKCnBlcl9tZ19maXRfdGVzdF90cmFpbiA8LSBtZXRfcGVyX21nX211bHRpQ1YgJT4lCiAgZmlsdGVyKHJ1bj09MSkgJT4lCiAgc2VsZWN0KGFscGhhLCBmaXQpICU+JQogIHJpZ2h0X2pvaW4ocGVyX21nX2ZpdF90ZXN0X3RyYWluKQoKcGVyX21nX2ZpdF90ZXN0X3RyYWluIDwtIHBlcl9tZ19maXRfdGVzdF90cmFpbiAlPiUKICBtdXRhdGUocHJlZF9mdWxsPW1hcDIoZml0LCBsYW1iZGEubWluLm1lYW4sIH4gcHJlZGljdCgueCwgcz0ueSwgbmV3eD1tZXRfcGVyX21nLlBDcykpLAogICAgICAgICBmdWxsX1I9bWFwX2RibChwcmVkX2Z1bGwsIH4gY29yKC54LCBsZWFmbGVuZ3RoJGxlYWZfYXZnX3N0ZCkpLAogICAgICAgICBmdWxsX01TRT1tYXBfZGJsKHByZWRfZnVsbCwgfiBtZWFuKChsZWFmbGVuZ3RoJGxlYWZfYXZnX3N0ZC0ueCleMikpKSAlPiUKICAKICBtdXRhdGUodHQ9bWFwMihsYW1iZGEubWluLm1lYW4sIGFscGhhLCB+IG11bHRpX3R0KGxhbWJkYT0ueCwgYWxwaGE9LnksIHg9bWV0X3Blcl9tZy5QQ3MpKSkKCgoKKHBlcl9tZ19maXRfdGVzdF90cmFpbiA8LSBwZXJfbWdfZml0X3Rlc3RfdHJhaW4gJT4lIHVubmVzdCh0dCkpCmBgYAoKYGBge3J9CnBlcl9tZ19maXRfdGVzdF90cmFpbiAlPiUKICBnZ3Bsb3QoYWVzKHg9YWxwaGEpKSArCiAgZ2VvbV9saW5lKGFlcyh5PWF2Z19jb3IpLCBjb2xvcj0icmVkIikgKwogIGdlb21fcG9pbnQoYWVzKHk9YXZnX2NvciksIGNvbG9yPSJyZWQiKSArCiAgZ2VvbV9saW5lKGFlcyh5PWF2Z19NU0UpLCBjb2xvcj0iYmx1ZSIpICsKICBnZW9tX3BvaW50KGFlcyh5PWF2Z19NU0UpLCBjb2xvcj0iYmx1ZSIpCmBgYAphbGwgYXJlIHNpbWlsYXIgZnJvbSAwLjIgb253YXJkcwoKIyMgbG9vayBhdCBmaXQ6CgpgYGB7cn0KYWxwaGFfcGVyX21nIDwtIC4yCgpiZXN0X3Blcl9tZyA8LSBwZXJfbWdfZml0X3Rlc3RfdHJhaW4gJT4lIGZpbHRlcihhbHBoYSA9PSBhbHBoYV9wZXJfbWcpIApiZXN0X3Blcl9tZ19maXQgPC0gYmVzdF9wZXJfbWckZml0W1sxXV0KYmVzdF9wZXJfbWdfbGFtYmRhIDwtIGJlc3RfcGVyX21nJGxhbWJkYS5taW4ubWVhbgoKcGVyX21nX2NvZWYudGIgPC0gY29lZihiZXN0X3Blcl9tZ19maXQsIHM9YmVzdF9wZXJfbWdfbGFtYmRhKSAlPiUgCiAgYXMubWF0cml4KCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhcj0iUEMiKSAlPiUKICByZW5hbWUoYmV0YT1gMWApCiAgCnBlcl9tZ19jb2VmLnRiICU+JSBmaWx0ZXIoYmV0YSE9MCkgJT4lIGFycmFuZ2UoYmV0YSkKCmBgYAoKcHJlZCBhbmQgb2JzCmBgYHtyfQpwbG90KGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkLCBiZXN0X3Blcl9tZyRwcmVkX2Z1bGxbWzFdXSkKY29yLnRlc3QobGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQsIGJlc3RfcGVyX21nJHByZWRfZnVsbFtbMV1dKSAjLjY1CmJlc3RfcGVyX21nJGZ1bGxfTVNFCmBgYAoKIyMgUGVyY2VudCB2YXJpYW5jZSBleHBsYWluZWQKCmBgYHtyfQpwZXJfbWdfdmFycyA8LSBwZXJfbWdfY29lZi50YiAlPiUgCiAgZmlsdGVyKGJldGEgIT0wLCBQQyE9IihJbnRlcmNlcHQpIikgJT4lCiAgcHVsbChQQykgJT4lIGMoImxlYWZfYXZnX3N0ZCIsIC4pCgpwZXJfbWdfcmVsaW1wIDwtIGxlYWZsZW5ndGggJT4lIHNlbGVjdChsZWFmX2F2Z19zdGQpICU+JSBjYmluZChtZXRfcGVyX21nLlBDcykgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZHBseXI6OnNlbGVjdChhbGxfb2YocGVyX21nX3ZhcnMpKSAlPiUKICBjYWxjLnJlbGltcCgpIAoKcGVyX21nX2NvZWYudGIgPC0gcGVyX21nX3JlbGltcEBsbWcgJT4lIGFzLm1hdHJpeCgpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJQQyIpICU+JQogIHJlbmFtZShQcm9wVmFyX21ldF9wZXJfbWc9VjEpICU+JQogIGZ1bGxfam9pbihwZXJfbWdfY29lZi50YikgJT4lCiAgYXJyYW5nZShkZXNjKFByb3BWYXJfbWV0X3Blcl9tZykpCgpwZXJfbWdfY29lZi50YgoKYGBgCgpDaGVja291dCB0aGUgcm90YXRpb25zLiAgCgpgYGB7cn0KbWV0X3Blcl9tZ19yb3RhdGlvbl9vdXQgPC0gbWV0X3Blcl9tZy5QQ19yb3RhdGlvbiAlPiUgCiAgcGl2b3RfbG9uZ2VyKC1tZXRhYm9saXRlLCBuYW1lc190bz0iUEMiLCB2YWx1ZXNfdG89ImxvYWRpbmciKSAlPiUKICBmaWx0ZXIoUEMgJWluJSBmaWx0ZXIocGVyX21nX2NvZWYudGIsIGJldGEhPTApJFBDICkgJT4lCiAgZ3JvdXBfYnkoUEMpICU+JQogICAgZmlsdGVyKCFzdHJfZGV0ZWN0KG1ldGFib2xpdGUsIi4qKGxlYWZ8cm9vdClfWzAtOV0qJCIpKSAlPiUKICBmaWx0ZXIoYWJzKGxvYWRpbmcpID49IDAuMDUpICU+JQogIGxlZnRfam9pbihwZXJfbWdfY29lZi50YiwgYnk9IlBDIikgJT4lCiAgYXJyYW5nZShkZXNjKGFicyhiZXRhKSksIGRlc2MoYWJzKGxvYWRpbmcpKSkgJT4lCiAgbXV0YXRlKG9yZ2FuPWlmZWxzZShzdHJfZGV0ZWN0KG1ldGFib2xpdGUsICJfbGVhZl8iKSwgImxlYWYiLCAicm9vdCIpLAogICAgICAgICB0cmFuc2Zvcm1hdGlvbj0ibm9ybWFsaXplZCIsCiAgICAgICAgIG1ldGFib2xpdGU9c3RyX3JlbW92ZShtZXRhYm9saXRlLCAibWV0X3Blcl9tZ18ocm9vdHxsZWFmKV8iKSwKICAgICAgICAgbWV0YWJvbGl0ZV9lZmZlY3Rfb25fbGVhZj1pZmVsc2UoYmV0YSpsb2FkaW5nPjAsICJpbmNyZWFzZSIsICJkZWNyZWFzZSIpKQptZXRfcGVyX21nX3JvdGF0aW9uX291dCAlPiUgIHdyaXRlX2NzdigiLi4vb3V0cHV0L0xlYWZfYXNzb2NpYXRlZF9tZXRhYm9saXRlc19ub3JtYWxpemVkX05PQkxBTksuY3N2IikKbWV0X3Blcl9tZ19yb3RhdGlvbl9vdXQKYGBgCgojIG5vbi1ub3JtYXpsaXplZAoKIyMgbXVsdGkgQ1YKCkZpdCAxMDEgQ1ZzIGZvciBlYWNoIG9mIDExIGFscGhhcwpgYGB7cn0Kc2V0LnNlZWQoMTI0NSkKCmZvbGRzIDwtIHRpYmJsZShydW49MToxMDEpICU+JSAKICBtdXRhdGUoZm9sZHM9bWFwKHJ1biwgfiBzYW1wbGUocmVwKDE6Niw0KSkpKQoKc3lzdGVtLnRpbWUgKG1ldF9hbXRfbXVsdGlDViA8LSBleHBhbmRfZ3JpZChydW49MToxMDAsIGFscGhhPXJvdW5kKHNlcSgwLDEsLjEpLDEpKSAlPiUKICAgICAgICAgICAgICAgbGVmdF9qb2luKGZvbGRzLCBieT0icnVuIikgJT4lCiAgICAgICAgICAgICAgIG11dGF0ZShmaXQ9bWFwMihmb2xkcywgYWxwaGEsIH4gY3YuZ2xtbmV0KHg9bWV0X2FtdC5QQ3MsIHk9bGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQsIGZvbGRpZCA9IC54LCBhbHBoYT0ueQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSkKICAgICAgICAgICAgICMsIGxhbWJkYT1leHAoc2VxKC01LDAsbGVuZ3RoLm91dCA9IDUwKSkgKSkpCikgIzEwMCBzZWNvbmRzCgpoZWFkKG1ldF9hbXRfbXVsdGlDVikKYGBgCgpmb3IgZWFjaCBmaXQsIHB1bGwgb3V0IHRoZSBtZWFuIGN2IGVycm9yLCBsYW1iZGEsIG1pbiBsYW1iZGEsIGFuZCAxc2UgbGFtYmRhIApgYGB7cn0KbWV0X2FtdF9tdWx0aUNWIDwtIG1ldF9hbXRfbXVsdGlDViAlPiUKICBtdXRhdGUoY3ZtPW1hcChmaXQsIG1hZ3JpdHRyOjpleHRyYWN0KCJjdm0iKSksCiAgICAgICAgIGxhbWJkYT1tYXAoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibGFtYmRhIikpLAogICAgICAgICBsYW1iZGEubWluPW1hcF9kYmwoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibGFtYmRhLm1pbiIgKSksCiAgICAgICAgIGxhbWJkYS4xc2U9bWFwX2RibChmaXQsIG1hZ3JpdHRyOjpleHRyYWN0KCJsYW1iZGEuMXNlIikpLAogICAgICAgICBuemVybz1tYXAoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibnplcm8iKSkKICApCgpoZWFkKG1ldF9hbXRfbXVsdGlDVikKYGBgCgoKbm93IGNhbGN1bGF0ZSB0aGUgbWVhbiBhbmQgc2VtIG9mIGN2bSBhbmQgbWluLDFzZSBsYWJtZGFzLiAgVGhlc2UgbmVlZCB0byBiZSBkb25lIHNlcGFyYXRlbHkgYmVjYXVzZSBvZiB0aGUgd2F5IHRoZSBncm91cGluZyB3b3JrcwpgYGB7cn0KbWV0X2FtdF9zdW1tYXJ5X2N2bSA8LSBtZXRfYW10X211bHRpQ1YgJT4lIGRwbHlyOjpzZWxlY3QoLWZpdCwgLWZvbGRzKSAlPiUgCiAgdW5uZXN0KGMoY3ZtLCBsYW1iZGEpKSAlPiUKICBncm91cF9ieShhbHBoYSwgbGFtYmRhKSAlPiUKICBzdW1tYXJpemUobWVhbmN2bT1tZWFuKGN2bSksIHNlbT1zZChjdm0pL3NxcnQobigpKSwgaGlnaD1tZWFuY3ZtK3NlbSwgbG93PW1lYW5jdm0tc2VtKQoKbWV0X2FtdF9zdW1tYXJ5X2N2bQpgYGAKCmBgYHtyfQptZXRfYW10X3N1bW1hcnlfbGFtYmRhIDwtIG1ldF9hbXRfbXVsdGlDViAlPiUgZHBseXI6OnNlbGVjdCgtZml0LCAtZm9sZHMsIC1jdm0pICU+JSAKICBncm91cF9ieShhbHBoYSkgJT4lCiAgc3VtbWFyaXplKAogICAgbGFtYmRhLm1pbi5zZD1zZChsYW1iZGEubWluKSwgCiAgICBsYW1iZGEubWluLm1lYW49bWVhbihsYW1iZGEubWluKSwKICAgICNsYW1iZGEubWluLm1lZD1tZWRpYW4obGFtYmRhLm1pbiksIAogICAgbGFtYmRhLm1pbi5oaWdoPWxhbWJkYS5taW4ubWVhbitsYW1iZGEubWluLnNkLAogICAgI2xhbWJkYS5taW4ubG93PWxhbWJkYS5taW4ubWVhbi1sYW1iZGEubWluLnNlbSwKICAgICNsYW1iZGEuMXNlLnNlbT1zZChsYW1iZGEuMXNlKS9zcXJ0KG4oKSksIAogICAgbGFtYmRhLjFzZS5tZWFuPW1lYW4obGFtYmRhLjFzZSksCiAgICAjbGFtYmRhLjFzZS5tZWQ9bWVkaWFuKGxhbWJkYS4xc2UpLCAKICAgICNsYW1iZGEuMXNlLmhpZ2g9bGFtYmRhLjFzZStsYW1iZGEuMXNlLnNlbSwKICAgICNsYW1iZGEuMXNlLmxvdz1sYW1iZGEuMXNlLWxhbWJkYS4xc2Uuc2VtLAogICAgbnplcm89bnplcm9bMV0sCiAgICBsYW1iZGE9bGFtYmRhWzFdCiAgKQoKbWV0X2FtdF9zdW1tYXJ5X2xhbWJkYQpgYGAKCgpwbG90IGl0CmBgYHtyfQptZXRfYW10X3N1bW1hcnlfY3ZtICU+JQogICNmaWx0ZXIoYWxwaGEhPTApICU+JSAjIHdvcnNlIHRoYW4gZXZlcnl0aGluZyBlbHNlIGFuZCB0aHJvd2luZyB0aGUgcGxvdHMgb2ZmCiAgZ2dwbG90KGFlcyh4PWxvZyhsYW1iZGEpLCB5PSBtZWFuY3ZtLCAgeW1pbj1sb3csIHltYXg9aGlnaCkpICsKICBnZW9tX3JpYmJvbihhbHBoYT0uMjUpICsKICBnZW9tX2xpbmUoYWVzKGNvbG9yPWFzLmNoYXJhY3RlcihhbHBoYSkpKSArCiAgZmFjZXRfd3JhcCh+IGFzLmNoYXJhY3RlcihhbHBoYSkpICsKICAgY29vcmRfY2FydGVzaWFuKHhsaW09KGMoLTUsMCkpKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1sb2cobGFtYmRhLm1pbi5tZWFuKSksIGFscGhhPS41LCBkYXRhPW1ldF9hbXRfc3VtbWFyeV9sYW1iZGEpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PWxvZyhsYW1iZGEubWluLmhpZ2gpKSwgYWxwaGE9LjUsIGRhdGE9bWV0X2FtdF9zdW1tYXJ5X2xhbWJkYSwgY29sb3I9ImJsdWUiKSAKCmBgYAoKCk1ha2UgYSBwbG90IG9mIE1TRSBhdCBtaW5pbXVtIGxhbWJkYSBmb3IgZWFjaCBhbHBoYQoKYGBge3J9Cm1ldF9hbXRfc3VtbWFyeV9jdm0gJT4lIAogIGdyb3VwX2J5KGFscGhhKSAlPiUKICBmaWx0ZXIocmFuayhtZWFuY3ZtLCB0aWVzLm1ldGhvZCA9ICJmaXJzdCIpPT0xKSAlPiUKICBnZ3Bsb3QoYWVzKHg9YWxwaGEseT1tZWFuY3ZtLHltaW49bG93LHltYXg9aGlnaCkpICsKICBnZW9tX3JpYmJvbihjb2xvcj1OQSwgZmlsbD0iZ3JheTgwIikgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkKYGBgCmRlY3JlYXNpbmcgdGhyb3VnaG91dCwgYWx0aG91Z2ggZGlmZmVyZW5jZXMgYXJlIHByZXR0eSBzbWFsbAoKUGxvdCB0aGUgbnVtYmVyIG9mIG56ZXJvIGNvZWZmaWNpZW50cwoKYGBge3J9Cm1ldF9hbXRfc3VtbWFyeV9sYW1iZGEgJT4lCiAgdW5uZXN0KGMobGFtYmRhLCBuemVybykpICU+JQogIGdyb3VwX2J5KGFscGhhKSAlPiUKICBmaWx0ZXIoYWJzKGxhbWJkYS5taW4ubWVhbi1sYW1iZGEpPT1taW4oYWJzKGxhbWJkYS5taW4ubWVhbi1sYW1iZGEpKSAgKSAlPiUKICB1bmdyb3VwKCkgJT4lCgpnZ3Bsb3QoYWVzKHg9YXMuY2hhcmFjdGVyKGFscGhhKSwgeT1uemVybykpICsKICBnZW9tX3BvaW50KCkgKwogIGdndGl0bGUoIk51bWJlciBvZiBub24temVybyBjb2VmZmljZW50cyBhdCBtaW5pbXVtIGxhbWJkYSIpICsKICB5bGltKDAsMzYpCmBgYApPSyBsZXQncyBkbyByZXBlYXRlZCB0ZXN0IHRyYWluIHN0YXJ0aW5nIGZyb20gdGhlc2UgQ1YgbGFtYmRhcwoKYGBge3J9Cm11bHRpX3R0IDwtIGZ1bmN0aW9uKGxhbWJkYSwgYWxwaGEsIG49MTAwMDAsIHNhbXBsZV9zaXplPTI0LCB0cmFpbl9zaXplPTIwLCB4LCB5PWxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkKSB7CiAgcHJpbnQobGFtYmRhKQogIHByaW50KGFscGhhKQp0dCA8LQogIHRpYmJsZShydW49MTpuKSAlPiUKICBtdXRhdGUodHJhaW49bWFwKHJ1biwgfiBzYW1wbGUoMTpzYW1wbGVfc2l6ZSwgdHJhaW5fc2l6ZSkpKSAlPiUKICBtdXRhdGUoZml0PW1hcCh0cmFpbiwgfiBnbG1uZXQoeD14Wy4sXSwgeT15Wy5dLCBsYW1iZGEgPSBsYW1iZGEsIGFscGhhID0gYWxwaGEgKSkpICU+JQogIAogIG11dGF0ZShwcmVkPW1hcDIoZml0LCB0cmFpbiwgfiBwcmVkaWN0KC54LCBuZXd4ID0geFstLnksXSkpKSAlPiUKICBtdXRhdGUoY29yPW1hcDJfZGJsKHByZWQsIHRyYWluLCB+IGNvcigueCwgeVstLnldKSAgKSkgJT4lCiAgbXV0YXRlKE1TRT1tYXAyX2RibChwcmVkLCB0cmFpbiwgfiBtZWFuKCh5Wy0ueV0gLSAueCleMikpKSAlPiUKICBzdW1tYXJpemUoCiAgICBudW1fbmE9c3VtKGlzLm5hKGNvcikpLCAKICAgIG51bV9sdF8wPXN1bShjb3I8PTAsIG5hLnJtPVRSVUUpLAogICAgYXZnX2Nvcj1tZWFuKGNvciwgbmEucm09VFJVRSksCiAgICBhdmdfTVNFPW1lYW4oTVNFKSkKdHQKfQoKYW10X2ZpdF90ZXN0X3RyYWluIDwtIG1ldF9hbXRfc3VtbWFyeV9sYW1iZGEgJT4lIAogIHNlbGVjdChhbHBoYSwgbGFtYmRhLm1pbi5tZWFuKQoKYW10X2ZpdF90ZXN0X3RyYWluIDwtIG1ldF9hbXRfbXVsdGlDViAlPiUKICBmaWx0ZXIocnVuPT0xKSAlPiUKICBzZWxlY3QoYWxwaGEsIGZpdCkgJT4lCiAgcmlnaHRfam9pbihhbXRfZml0X3Rlc3RfdHJhaW4pCgphbXRfZml0X3Rlc3RfdHJhaW4gPC0gYW10X2ZpdF90ZXN0X3RyYWluICU+JQogIG11dGF0ZShwcmVkX2Z1bGw9bWFwMihmaXQsIGxhbWJkYS5taW4ubWVhbiwgfiBwcmVkaWN0KC54LCBzPS55LCBuZXd4PW1ldF9hbXQuUENzKSksCiAgICAgICAgIGZ1bGxfUj1tYXBfZGJsKHByZWRfZnVsbCwgfiBjb3IoLngsIGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkKSksCiAgICAgICAgIGZ1bGxfTVNFPW1hcF9kYmwocHJlZF9mdWxsLCB+IG1lYW4oKGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkLS54KV4yKSkpICU+JQogIAogIG11dGF0ZSh0dD1tYXAyKGxhbWJkYS5taW4ubWVhbiwgYWxwaGEsIH4gbXVsdGlfdHQobGFtYmRhPS54LCBhbHBoYT0ueSwgeD1tZXRfYW10LlBDcykpKQoKCgooYW10X2ZpdF90ZXN0X3RyYWluIDwtIGFtdF9maXRfdGVzdF90cmFpbiAlPiUgdW5uZXN0KHR0KSkKYGBgCgpgYGB7cn0KYW10X2ZpdF90ZXN0X3RyYWluICU+JQogIGdncGxvdChhZXMoeD1hbHBoYSkpICsKICBnZW9tX2xpbmUoYWVzKHk9YXZnX2NvciksIGNvbG9yPSJyZWQiKSArCiAgZ2VvbV9wb2ludChhZXMoeT1hdmdfY29yKSwgY29sb3I9InJlZCIpICsKICBnZW9tX2xpbmUoYWVzKHk9YXZnX01TRSksIGNvbG9yPSJibHVlIikgKwogIGdlb21fcG9pbnQoYWVzKHk9YXZnX01TRSksIGNvbG9yPSJibHVlIikKYGBgCm5vdCBhIGJpZyBkaWZmZXJlbmNlIGFmdGVyIDAuMgoKIyMgbG9vayBhdCBmaXQ6CgpgYGB7cn0KYWxwaGFfYW10IDwtIC43CgpiZXN0X2FtdCA8LSBhbXRfZml0X3Rlc3RfdHJhaW4gJT4lIGZpbHRlcihhbHBoYSA9PSBhbHBoYV9hbXQpIApiZXN0X2FtdF9maXQgPC0gYmVzdF9hbXQkZml0W1sxXV0KYmVzdF9hbXRfbGFtYmRhIDwtIGJlc3RfYW10JGxhbWJkYS5taW4ubWVhbgoKYW10X2NvZWYudGIgPC0gY29lZihiZXN0X2FtdF9maXQsIHM9YmVzdF9hbXRfbGFtYmRhKSAlPiUgCiAgYXMubWF0cml4KCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhcj0iUEMiKSAlPiUKICByZW5hbWUoYmV0YT1gMWApCiAgCmFtdF9jb2VmLnRiICU+JSBmaWx0ZXIoYmV0YSE9MCkgJT4lIGFycmFuZ2UoYmV0YSkKCmBgYAoKcHJlZCBhbmQgb2JzCmBgYHtyfQpwbG90KGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkLCBiZXN0X2FtdCRwcmVkX2Z1bGxbWzFdXSkKY29yLnRlc3QobGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQsIGJlc3RfYW10JHByZWRfZnVsbFtbMV1dKSAjLjY0CmJlc3RfYW10JGZ1bGxfTVNFCmBgYAoKIyMgUGVyY2VudCB2YXJpYW5jZSBleHBsYWluZWQKCmBgYHtyfQphbXRfdmFycyA8LSBhbXRfY29lZi50YiAlPiUgCiAgZmlsdGVyKGJldGEgIT0wLCBQQyE9IihJbnRlcmNlcHQpIikgJT4lCiAgcHVsbChQQykgJT4lIGMoImxlYWZfYXZnX3N0ZCIsIC4pCgojIGJlY2F1c2UgdGhlcmUgaXMgb25seSBvbmUgZXhwbGFuYXRvcnkgdmFyaWFibGUsIHdlIGRvbid0IG5lZWQgdG8gKGFuZCBjYW4ndCkgdXNlIHJlbGltcAphbXRfcmVsaW1wIDwtIGxlYWZsZW5ndGggJT4lIHNlbGVjdChsZWFmX2F2Z19zdGQpICU+JSBjYmluZChtZXRfYW10LlBDcykgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZHBseXI6OnNlbGVjdChhbGxfb2YoYW10X3ZhcnMpKSAlPiUgY29yKCkgJT4lIG1hZ3JpdHRyOjpleHRyYWN0KDEsMikgJT4lIG1hZ3JpdHRyOjptdWx0aXBseV9ieSguLC4pCmFtdF9yZWxpbXAgPC0gdGliYmxlKFBDPXN0cl9zdWJzZXQoYW10X3ZhcnMsICJQQyIpLAogICAgICAgICAgICAgICAgICAgICBQcm9wVmFyX21ldF9hbXQ9YW10X3JlbGltcCkKCgphbXRfY29lZi50YiA8LSBhbXRfcmVsaW1wICU+JSAKICBmdWxsX2pvaW4oYW10X2NvZWYudGIpICU+JQogIGFycmFuZ2UoZGVzYyhQcm9wVmFyX21ldF9hbXQpKQoKYW10X2NvZWYudGIKCmBgYAoKQ2hlY2tvdXQgdGhlIHJvdGF0aW9ucy4gIAoKYGBge3J9Cm1ldF9hbXRfcm90YXRpb25fb3V0IDwtIG1ldF9hbXQuUENfcm90YXRpb24gJT4lIAogIHBpdm90X2xvbmdlcigtbWV0YWJvbGl0ZSwgbmFtZXNfdG89IlBDIiwgdmFsdWVzX3RvPSJsb2FkaW5nIikgJT4lCiAgZmlsdGVyKFBDICVpbiUgZmlsdGVyKGFtdF9jb2VmLnRiLCBiZXRhIT0wKSRQQyApICU+JQogIGdyb3VwX2J5KFBDKSAlPiUKICAgIGZpbHRlcighc3RyX2RldGVjdChtZXRhYm9saXRlLCIuKihsZWFmfHJvb3QpX1swLTldKiQiKSkgJT4lCiAgZmlsdGVyKGFicyhsb2FkaW5nKSA+PSAwLjA1KSAlPiUKICBsZWZ0X2pvaW4oYW10X2NvZWYudGIsIGJ5PSJQQyIpICU+JQogIGFycmFuZ2UoZGVzYyhhYnMoYmV0YSkpLCBkZXNjKGFicyhsb2FkaW5nKSkpICU+JQogIG11dGF0ZShvcmdhbj1pZmVsc2Uoc3RyX2RldGVjdChtZXRhYm9saXRlLCAiX2xlYWZfIiksICJsZWFmIiwgInJvb3QiKSwKICAgICAgICAgdHJhbnNmb3JtYXRpb249InJhdyIsCiAgICAgICAgIG1ldGFib2xpdGU9c3RyX3JlbW92ZShtZXRhYm9saXRlLCAibWV0X2FtdF8ocm9vdHxsZWFmKV8iKSwKICAgICAgICAgbWV0YWJvbGl0ZV9lZmZlY3Rfb25fbGVhZj1pZmVsc2UoYmV0YSpsb2FkaW5nPjAsICJpbmNyZWFzZSIsICJkZWNyZWFzZSIpKQptZXRfYW10X3JvdGF0aW9uX291dCAlPiUgIHdyaXRlX2NzdigiLi4vb3V0cHV0L0xlYWZfYXNzb2NpYXRlZF9tZXRhYm9saXRlc19yYXdfTk9CTEFOSy5jc3YiKQoKbWV0X2FtdF9yb3RhdGlvbl9vdXQKYGBg